react

[Testing] 2. 프론트엔드, 어떻게 테스트 할 것인가

10 min read|20. 1. 3.

react-testing-logo

앞서 프론트엔드 테스트 코드를 작성하면서 마주할 수 있는 몇 가지에 대해 이야기했다. 이번 편에서는 다시 테스트에 대한 내용으로 돌아가 앞서 다룬 이야기들을 기반으로 프론트엔드 입장에서 테스트에 대한 부분을 되짚어 보려고 한다.

테스트의 기본

테스트는 아래의 내용을 만족해야 한다.

  • 테스트 케이스(Test Case)는 반드시 참(True), 거짓(False)을 반환해야 한다.
  • 이 참, 거짓은 반드시 어떠한 가정을 통해 포함한다.
  • 이 가정에 따라 출력된 **값(result)**이 예상(expect)한 값과 일치하는지에 따라 참, 거짓을 결정한다.
  • 테스트의 결과는 외부에 있는 어떤 요소에 의해 결정되는 부분이 없어야 하며 오로지 가정에 의해서만 결정되어야 한다.
  • 즉, 가정이 변하지 않는 이상 테스트의 결과는 항상 동일해야 한다.
  • 가정은 테스트 케이스의 의도를 포함하며 각 테스트 케이스 하나 당 하나의 의도만을 포함해야 한다.

테스트 케이스 만들기

Given-When-Then pattern

Arrange-Act-Assert pattern이라는 것도 있다던데, 일단 필자는 이 둘의 차이를 이해하지 못한다. 약간의 의미가 다를 뿐, 비슷하다고 생각하고 Given, When, Then에 맞춰서 테스트 코드를 작성하고 있다.

test('should $1', () => {
  // Given
  const data = $4

  // When
  const result = $3

  // Then
  expect(result).toEqual($2)
})

테스트를 작성할 때는 위 Code Snippet을 만들어두고 1번부터 4번까지 흐름대로 작성한다.

  1. 테스트 케이스의 목적을 작성한다.
  2. 최종적으로 확인해야하는 부분, 즉 예상하는 값을 작성한다.
  3. 어떠한 flow를 검증할지 작성한다.
  4. 3번 검증을 위해 어떤 가정이 필요한지 작성한다.

테스트 코드를 작성하는 것이 익숙하지 않은 분이라면 위 코드 조각(snippet)을 추천한다. 테스트 코드를 작성하는 것이 익숙하지 않을 때 어떤 순서로 어떻게 작성해야하는지 계속 버벅거리게 된다. 비즈니스 코드와는 달리 그 흐름이 익숙하지 않아서 라고 생각되는데 그런 부분을 코드 작성의 순서를 강제함으로써 해결할 수 있다.

  • 2번) 대부분 toEqual로 작성하지만 toBe, toBeFalsy 등이 될 수 있다.
  • 3번) result에는 주어진(given)데이터를 기반으로 어떠한 행동을 취했을 때 검증해야하는 값이 온다. 즉, 어떠한 함수를 호출했을 때의 반환값이거나 결과가 된다.
  • 4번) data라고 했지만 이 변수는 payload가 될 수도 있고 mocking하는 state object 일 수 있다.

테스트 분류

아시다시피 테스트가 작성되는 목적에 따라 그 이름이 달라진다. 각각의 테스트는 목적에 따라서 테스트 할 대상을 정하게 되고 그에 따라 특성을 갖는다.

단위 테스트 (Unit Test)

작성한 애플리케이션에서 테스트 가능한 가장 작은 단위의 코드를 테스트하는 기법이다. 여러 작은 단위의 테스트들이 독립적으로 참, 거짓을 판단하기 때문에 테스트가 실패했을 경우, 어느 부분이 문제인지 빠르게 파악할 수 있지만 애플리케이션의 전체적인 플로우가 정상임을 보장하지는 않는다.

통합 테스트 (Integration Test)

애플리케이션에서 두 가지 이상의 요소가 함께 상호 작용할 때, 개발자가 의도한 대로 동작하는지 테스트하는 기법이다. 프론트엔드 개발 환경에서 봤을 때, Store에 연결(connect)된 Component를 테스트하는 경우를 예로 들 수 있을 것 같다.

하지만 두 가지 이상의 요소가 함께 상호 작용하는 부분은 맞지만 하나의 가정을 테스트하기 때문에 이것을 통합 테스트라고 할 수 있을지 의문이다. 어쩌면 유닛 테스트와 통합 테스트의 경계는 사실 모호한 것일 수 있다.

E2E 테스트 (End to End Test)

기능 테스트(Funtional Test)라고도 불리는 이 테스팅 기법은 말 그대로 끝에서 끝까지 테스트하는 기법이다. 사용자가 직접 애플리케이션을 사용하는 것처럼 동작하도록 스크립트를 작성하고 이것을 실제 실행시켜보면서 기대한대로 동작하는지 검증할 수 있다.

정말 멋진 테스트 방법이다. 프론트엔드 환경에서 E2E 테스트를 지원하기 위한 여러 도구들이 존재한다.(cypress, testcafe, nightwatch)

테스트 비용(Cost)

테스트 결과가 애플리케이션이 정상 동작함을 보장하는데 있어서 테스트하는 대상이 넓을수록 그 신뢰성을 보장한다. 하지만 그만큼의 ‘비용’이 들어간다. 처음 테스트 코드를 작성할 때 뿐만 아니라 운영 중에 테스트를 유지보수하는데 큰 비용이 들어간다.

여러 테스트들을 작성하여 변경 사항에 대하여 촘촘한 그물망을 만드는 것이 좋아보인다. 하지만 ROI를 계산해봤을 때, 그것이 과연 맞는지 생각해볼 필요가 있다.

비즈니스 로직과 UI 렌더링의 분리

테스트를 하기 전 비즈니스 설계가 제대로 되어야 한다. 앞서 컴포넌트 렌더링과 관련된 단위 테스트는 작성하지 않겠다고 선언했다.

그런데 컴포넌트에 비즈니스 로직이 들어가게 되면 그 부분은 테스트를 수 없게 되며 발생할 수 있는 버그로부터 안전하다는 것을 보장받을 수 없게 된다.

자연스럽게 비즈니스 로직을 custom hooks, middleware, selector, reducer 로 옮기게 되고 컴포넌트는 그저 화면을 그리는 일만 하게 된다. Hooks API가 들어오면서 컴포넌트의 비즈니스 로직을 분리하여 관리할 수 있게 되었다.

물론 몇 가지 예외사항이 있을 수 있다.

  • 사용자 동작에 대한 alert 띄우기
  • anchor 태그 클릭을 통한 외부 페이지로의 이동

그러나 대부분의 상황에 대해서는 전부 컴포넌트에서 벗어날 수 있는 비즈니스 로직이다.

  • 특정 버튼을 클릭하였을 때, 어떤 조건에 따라서 팝업이 노출해야 한다.
  • api 호출 시, 에러가 발생했을 때 알맞은 에러 메세지를 노출해야 한다.
  • 특정 조건에 따라 버튼이 활성화되고 비활성화된다.

이러한 비즈니스 로직들을 컴포넌트에서 걷어내야 컴포넌트 단위 테스트를 작성하지 않아도 된다.

마무리

이번 편에서도 말만 많았다. 다음 장부터는 진짜 코드로 살펴보기로 하자.

| | | | :---: | :-------------------------------------------------------------------------------: | | Next | 3. Store와 비즈니스 로직 테스트 | | Intro | 0. 시리즈를 들어가며 |